package com.xy.tool.report.view;

import com.xy.tool.report.Crosstab;
import com.xy.tool.report.function.ColRowIndexConsumer;
import com.xy.tool.report.function.RowConsumer;
import com.xy.tool.report.function.TableConsumer;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by zsp on 2018/12/7.
 */
public class CrosstabView {

    private Crosstab crosstab;
    private int maxTableRowSize;
    private int maxTableColSize;
    private List<Table> tableList = new ArrayList<>();
    private int[] columnWidthArray;

    private List<ColRowIndexConsumer> colRowIndexChangedListenerList = new ArrayList<>();
    private List<RowConsumer> afterHeaderRowBuildListenerList = new ArrayList<>();
    private List<RowConsumer> afterDataRowBuildListenerList = new ArrayList<>();
    private List<TableConsumer> beforeTableBuildListenerList = new ArrayList<>();
    private List<TableConsumer> afterTableBuildListenerList = new ArrayList<>();

    public CrosstabView(Crosstab crosstab) {
        this.crosstab = crosstab;
    }

    public CrosstabView(Crosstab crosstab, int maxTableColSize) {
        this(crosstab);
        if(maxTableColSize < 0) {
            throw new IllegalArgumentException();
        }
        this.maxTableColSize = maxTableColSize;
    }

    public CrosstabView(Crosstab crosstab, int maxTableRowSize, int maxTableColSize) {
        this(crosstab, maxTableColSize);
        if(maxTableRowSize < 0) {
            throw new IllegalArgumentException();
        }
        this.maxTableRowSize = maxTableRowSize;
    }

    private void fireColRowIndexChange(int colIndex, int rowIndex) {
        if(!CollectionUtils.isEmpty(colRowIndexChangedListenerList)) {
            for(ColRowIndexConsumer listener : colRowIndexChangedListenerList) {
                listener.accept(colIndex, rowIndex);
            }
        }
    }

    private void fireAfterHeaderRowBuild(Row row) {
        if(!CollectionUtils.isEmpty(afterHeaderRowBuildListenerList)) {
            for(RowConsumer listener : afterHeaderRowBuildListenerList) {
                listener.accept(0, row, null);
            }
        }
    }

    private void fireAfterDataRowBuild(int rowIndex, Row row, int[] rowData) {
        if(!CollectionUtils.isEmpty(afterDataRowBuildListenerList)) {
            for(RowConsumer listener : afterDataRowBuildListenerList) {
                listener.accept(rowIndex, row, rowData);
            }
        }
    }

    private void fireBeforeTableBuild(Table table) {
        if(!CollectionUtils.isEmpty(beforeTableBuildListenerList)) {
            for(TableConsumer listener : beforeTableBuildListenerList) {
                listener.accept(table, null);
            }
        }
    }

    private void fireAfterTableBuild(Table table, int[][] tableData) {
        if(!CollectionUtils.isEmpty(afterTableBuildListenerList)) {
            for(TableConsumer listener : afterTableBuildListenerList) {
                listener.accept(table, tableData);
            }
        }
    }

    public void build(String tableName, Cell leftTopColumn) {
        build(tableName, leftTopColumn, true, true);
    }

    public void build(String tableName, Cell leftTopColumn, boolean withColHeader, boolean withRowHeader) {
        crosstab.fill();

        int tableRowSize = maxTableRowSize;
        int tableColSize = maxTableColSize;
        if(tableRowSize == 0 && tableColSize == 0) {
            buildTable(tableName, leftTopColumn, 0, 0, withColHeader, withRowHeader);
        } else if(tableRowSize == 0 && tableColSize > 0) {
            int colSize = crosstab.getColSize();
            for (int i = 0, colPageNo = 1; i < colSize; i += tableColSize, colPageNo++) {
                buildTable(tableName + "-" + colPageNo, leftTopColumn, 0, i,
                        withColHeader, withRowHeader);
            }
        } else if(tableRowSize > 0 && tableColSize == 0) {
            int rowSize = crosstab.getRowSize();
            for (int j = 0, rowPageNo = 1; j < rowSize; j += tableRowSize, rowPageNo++) {
                buildTable(tableName + "-" + rowPageNo, leftTopColumn, j, 0,
                        withColHeader, withRowHeader);
            }
        } else {
            int rowSize = crosstab.getRowSize();
            int colSize = crosstab.getColSize();
            for (int i = 0, colPageNo = 1; i < colSize; i += tableColSize, colPageNo++) {
                for (int j = 0, rowPageNo = 1; j < rowSize; j += tableRowSize, rowPageNo++) {
                    buildTable(tableName + "-" + colPageNo + "-" + rowPageNo, leftTopColumn, j, i,
                            withColHeader, withRowHeader);
                }
            }
        }
    }

    private void buildTable(String tableName, Cell leftTopCell, int rowOffset, int colOffset,
                            boolean withColHeader, boolean withRowHeader) {
        fireColRowIndexChange(colOffset, rowOffset);

        Table table = new Table(tableName);
        tableList.add(table);

        fireBeforeTableBuild(table);

        String[] rowHeaderNames = crosstab.getVerticalHeaderNames();
        int rowSize = rowHeaderNames.length;
        int tableRowSize = maxTableRowSize > 0 ? maxTableRowSize : rowSize;
        int endRowIndex = rowOffset + tableRowSize;
        String[] colHeaderNames = crosstab.getHorizontalHeaderNames();
        int colSize = colHeaderNames.length;
        int tableColSize = maxTableColSize > 0 ? maxTableColSize : colSize;
        int endColIndex = colOffset + tableColSize;

        Row row;
        if(withColHeader) {
            row = new Row();
            table.getRowList().add(row);
            if(withRowHeader && leftTopCell != null) {
                row.addCell(leftTopCell);
            }
            for (int colIndex = colOffset; colIndex < endColIndex; colIndex++) {
                if (colIndex < colSize) {
                    row.addCell(new Cell(colHeaderNames[colIndex]));
                } else {
                    row.addCell(Cell.empty());
                }
            }
            fireAfterHeaderRowBuild(row);
        }

        int[][] data = crosstab.getData();
        int[][] tableData = new int[endRowIndex - rowOffset][endColIndex - colOffset];
        int number;
        for (int rowIndex = rowOffset, i = 0; rowIndex < endRowIndex; rowIndex++, i++) {
            row = new Row();
            table.getRowList().add(row);
            if(withRowHeader) {
                if (rowIndex < rowSize) {
                    row.addCell(new Cell(rowHeaderNames[rowIndex]));
                } else {
                    row.addCell(Cell.empty());
                }
            }
            for (int colIndex = colOffset, j = 0; colIndex < endColIndex; colIndex++, j++) {
                if(colIndex < colSize) {
                    number = data[rowIndex][colIndex];
                    row.addCell(new Cell(number));
                    tableData[i][j] = number;
                } else {
                    row.addCell(Cell.empty());
                }
            }
            if(rowIndex < rowSize) {
                fireAfterDataRowBuild(rowIndex, row, tableData[i]);
            }
        }

        fireAfterTableBuild(table, tableData);
    }

    public List<Table> getTableList() {
        return tableList;
    }

    public int[] getColumnWidthArray() {
        return columnWidthArray;
    }

    public void setColumnWidthArray(int[] columnWidthArray) {
        this.columnWidthArray = columnWidthArray;
    }

    public void addColRowIndexChangedListener(ColRowIndexConsumer listener) {
        colRowIndexChangedListenerList.add(listener);
    }

    public void removeColRowIndexChangedListener(ColRowIndexConsumer listener) {
        colRowIndexChangedListenerList.remove(listener);
    }

    public void addAfterHeaderRowBuildListener(RowConsumer listener) {
        afterHeaderRowBuildListenerList.add(listener);
    }

    public void removeAfterHeaderRowBuildListener(RowConsumer listener) {
        afterHeaderRowBuildListenerList.remove(listener);
    }

    public void addAfterDataRowBuildListener(RowConsumer listener) {
        afterDataRowBuildListenerList.add(listener);
    }

    public void removeAfterDataRowBuildListener(RowConsumer listener) {
        afterDataRowBuildListenerList.remove(listener);
    }

    public void addBeforeTableBuildListener(TableConsumer listener) {
        beforeTableBuildListenerList.add(listener);
    }

    public void removeBeforeTableBuildListener(TableConsumer listener) {
        beforeTableBuildListenerList.remove(listener);
    }

    public void addAfterTableBuildListener(TableConsumer listener) {
        afterTableBuildListenerList.add(listener);
    }

    public void removeAfterTableBuildListener(TableConsumer listener) {
        afterTableBuildListenerList.remove(listener);
    }

}
